package net.alche.sdd.fatis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.hutool.core.bean.BeanUtil;
import net.alche.al.util.AC;
import net.alche.al.util.Far;
import net.alche.fa.core.FaException;
import net.alche.fa.core.SageStone;
import net.alche.fa.core.doing.IfDo;
import net.alche.fa.core.model.FaValue;
import net.alche.fa.core.model.Logic;
import net.alche.fa.core.parse.FavaParseUtil;
import net.alche.sdd.fatis.macro.SddFatisMacroForEach;
import net.alche.sdd.fatis.macro.SddFatisMacroIf;
import net.alche.sdd.fatis.macro.SddFatisMacroSwitch;

/**
 * 宏替换
 */
@SuppressWarnings("all")
public class SddFatisMacro {
  public static final Logger logger = LoggerFactory.getLogger(SddFatisMacro.class);
  String source = null;
  List<String> sqlCuts = null;
  boolean isBatchInsert = false;
  String tosql = null;
  List<SddFatisPrepared> preparedList = null;
  Integer autoIdVal = null;
  Map<String, Object> params = null;
  Object paramBean = null;
  List<Object> paramBeanLs = null;
  int paramBeanLsIndex = 0;
  SageStone paramBeanSage = null;

  SddFatisMacroIf macroIf = null;
  SddFatisMacroSwitch macroSwitch = null;
  SddFatisMacroForEach macroForeach = null;
  int forEachLv = -1;
  String autoIdCol = null; // 自增主键
  boolean isSet = false;

  public SddFatisMacro(String source) {
    this.source = source;
  }

  public SddFatisMacro(String source, Map mp) {
    this.source = source;
    putMap(mp);
  }

  public String getAutoIdCol() {
    return this.autoIdCol;
  }

  public void setAutoIdCol(String idCol) {
    autoIdCol = idCol;
  }

  public void setAutoIdVal(Integer id) throws FaException {
    autoIdVal = id;
    if (paramBean != null) {
      BeanUtil.setFieldValue(paramBean, autoIdCol, id);
    }
    if (params != null) {
      params.put(autoIdCol, id);
    }
    if (paramBeanSage != null) {
      FaValue fav = paramBeanSage.getVar(autoIdCol);
      if (fav == null) {
        throw new FaException(FaException.NO_DEFINE_VAR, "");
      }
      fav.aiData(id);
    }
    if (paramBeanLs != null) {
      Object obj = paramBeanLs.get(paramBeanLsIndex++);
      BeanUtil.setFieldValue(obj, autoIdCol, id);
    }
  }

  public Integer getAutoIdVal() {
    return autoIdVal;
  }

  public SddFatisMacro t() {
    analyse();
    return this;
  }

  void analyse() {
    Stream<String> lies = this.source.lines();
    lies.forEach(line -> {
      analyseLine(line);
    });
    if (macroForeach != null) {
      if (tosql == null)
        tosql = "";
      tosql += macroForeach.getSql();
    }

  }

  void cutSql(String statement) {
    if (sqlCuts == null)
      sqlCuts = new ArrayList<>();
    sqlCuts.add(tosql);
    sqlCuts.add(statement);
    tosql = null;
  }

  void analyseLine(String line) {
    String txt = line.trim();
    int lv = AC.str.lv(line);
    // logger.info("line: {}", line);
    if (macroIf != null && macroIf.getContinueLv() != -1) {
      if (macroIf.needContinue(lv))
        return;
    }
    if (macroSwitch != null && macroSwitch.getContinueLv() != -1) {
      if (macroSwitch.needContinue(lv))
        return;
    }
    if (txt.startsWith("s-")) {
      // 1. s-if
      if (txt.startsWith("s-if ")) {
        if (!sdo_if(txt)) {
          macroIf.setContinue(lv);
        }
      }
      // 2. s-where
      else if (txt.startsWith("s-where")) {
        sdo_where(line);
        isSet = false;
      }
      // 3. s-foreach
      else if (txt.startsWith("s-foreach ")) {
        macroForeach = sdo_foreach(line);
        macroForeach.lv = lv;
      }
      // 4. s-set
      else if (txt.startsWith("s-set")) {
        sdo_set(line);
        isSet = true;
      }
      // 5. s-switch
      else if (txt.startsWith("s-switch ")) {
        sdo_switch(txt);
      } else if (txt.startsWith("s-switch-case ")) {
        macroSwitch.doSwitchCaseLine(txt, lv);
      } else if (txt.startsWith("s-switch-deft")) {
        macroSwitch.doSwitchDeft(lv);
      }
      return;
    } else {
      // done-foreach
      if (macroForeach != null && macroForeach.lv != -1) {
        if (macroForeach.lv < lv) {
          macroForeach.addLine(line);
          return;
        } else {
          tosql += macroForeach.getSql();
          macroForeach = null;
        }
      }
      List<String> list = AC.reg.matchers("(#|\\$)\\{[\\w\\.\\[\\]\\ ]+\\}", line);
      if (list != null && !list.isEmpty()) {
        for (String macro : list) {
          if (macro.startsWith("$")) {
            Object obj = getSqlParam(macro);
            if (obj != null) {
              line = line.replace(macro, obj.toString());
            }
          } else {
            line = line.replace(macro, "?");
            addPrepared(getSqlParam(macro));
          }
        }
      }
    }
    if (line != null) {
      if (isSet) {
        line += ",";
      }
      tosql = tosql == null ? line : tosql + "\n" + line;
    }
  }

  // #{hehe}
  public Object getSqlParam(String macro) {
    String key = macro.substring(2, macro.length() - 1);
    return getParam(key);
  }

  public String getSqlMacroName(String macro) {
    return macro.substring(2, macro.length() - 1);
  }

  // sdo--------------
  SddFatisMacroForEach sdo_foreach(String line) {
    Map<String, String> attrMp = FavaParseUtil.getAttrs(line);
    Object obj = getParam(attrMp.get("collection"));
    if (obj == null) {
      return new SddFatisMacroForEach(this, null);
    }
    SddFatisMacroForEach mc = new SddFatisMacroForEach(this, obj);
    mc.setMap(attrMp);
    return mc;
  }

  void sdo_where(String line) {
    cutSql("s-where");
  }

  void sdo_set(String line) {
    cutSql("s-set");
  }

  void sdo_switch(String line) {
    Map<String, String> attrMp = FavaParseUtil.getAttrs(line);
    macroSwitch = new SddFatisMacroSwitch(this, attrMp.get("var"));
  }

  boolean sdo_if(String line) {
    line = line.replaceFirst("s-if ", "");
    String[] sp = line.split(" ");
    Logic logic = FavaParseUtil.getLogic(line);
    if (macroIf == null) {
      macroIf = new SddFatisMacroIf(this);
    }
    if (macroIf.getBoolVal(logic)) {
      return true;
    }
    return false;
  }

  public String getSql() {
    if (sqlCuts != null) {
      String sql = "";
      sqlCuts.add(tosql);
      for (int i = 0; i < sqlCuts.size(); i++) {
        String item = sqlCuts.get(i);
        if (item == null)
          continue;
        if (item.equals("s-where")) {
          if (sqlCuts.size() < i + 2) {
            continue;
          }
          sql += " where ";
          if (sqlCuts.size() > i + 1) {
            String sqlNext = sqlCuts.get(i + 1);
            if (sqlNext != null) {
              sqlNext = sqlNext.trim();
              if (sqlNext.startsWith("and ")) {
                sqlNext = sqlNext.replaceFirst("and ", "");
              }
              sql += sqlNext;
            }
          }
          i++;
          continue;
        }
        // set
        if (item.equals("s-set")) {
          if (sqlCuts.size() < i + 2) {
            continue;
          }
          sql += " set ";
          String sqlNext = sqlCuts.get(i + 1);// .trim();
          if (sqlNext == null) {
            continue;
          }
          sqlNext = sqlNext.trim();
          if (sqlNext.endsWith(",")) {
            sqlNext = sqlNext.substring(0, sqlNext.length() - 1);
          }
          sql += sqlNext;
          i++;
          continue;
        }
        sql += item;
      }
      // Far.log("sql", sql);
      return sql;
    } else {
      // Far.log("sql", tosql);
    }
    return tosql;
    // return null;
  }

  public List<SddFatisPrepared> getPreparedList() {
    return preparedList;
  }

  public void addPrepared(Object obj) {
    if (preparedList == null) {
      preparedList = new ArrayList<>();
    }
    preparedList.add(new SddFatisPrepared(obj));
  }

  public SddFatisMacro putMap(Map val) {
    this.params = val;
    return this;
  }

  public SddFatisMacro putBean(Object bean) {
    if (bean != null && bean instanceof SageStone st) {
      this.paramBeanSage = st;
    } else {
      this.paramBean = bean;
    }
    return this;
  }

  public SddFatisMacro putBeanLs(List<Object> beanLs) {
    paramBeanLs = beanLs;
    return this;
  }

  public SddFatisMacro put(String col, Object val) {
    if (this.params == null) {
      this.params = new HashMap<>();
    }
    if (isBatchInsert && val instanceof List toLs) {
      paramBeanLs = toLs;
    }
    this.params.put(col, val);
    return this;
  }

  public Object getParam(String val) {
    if (params != null) {
      return Far.java.checkFaValueObj(params.get(val));
    }
    if (paramBean != null) {
      try {
        return BeanUtil.getFieldValue(paramBean, val);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
    if (paramBeanSage != null) {
      FaValue fav = paramBeanSage.getVar(val);
      if (fav != null && !fav.isNull()) {
        return fav.getJava();
      }
    }
    return null;
  }

  public SddFatisMacro isBatch() {
    isBatchInsert = true;
    return this;
  }
}
